#include "../common/include/encode_parameter.h"
#include "../engine/include/converter.h"
#include <filesystem>
#include <fstream>
#include <gtest/gtest.h>
#include <iostream>
#include <memory>
#include <string>

// Test fixture for transcoder tests
class TranscoderTest : public ::testing::Test {
protected:
    void SetUp() override {
        // Create test directory
        test_dir_ = std::filesystem::temp_directory_path() / "transcoder_test";
        std::filesystem::create_directories(test_dir_);

        // Copy test media file to test directory
        std::string test_file = std::string(TEST_MEDIA_PATH) + "/test.mp4";
        if (std::filesystem::exists(test_file)) {
            std::filesystem::copy_file(test_file, test_dir_ / "test.mp4",
                                       std::filesystem::copy_options::overwrite_existing);
        } else {
            FAIL() << "Test media file not found at: " << test_file;
        }

        // Copy test image files if they exist
        std::string test_png = std::string(TEST_MEDIA_PATH) + "/test.png";
        if (std::filesystem::exists(test_png)) {
            std::filesystem::copy_file(test_png, test_dir_ / "test.png",
                                       std::filesystem::copy_options::overwrite_existing);
        }

        std::string test_jpg = std::string(TEST_MEDIA_PATH) + "/test.jpg";
        if (std::filesystem::exists(test_jpg)) {
            std::filesystem::copy_file(test_jpg, test_dir_ / "test.jpg",
                                       std::filesystem::copy_options::overwrite_existing);
        }

        // Copy test_av.mp4 (stream 0 = audio, stream 1 = video) if it exists
        std::string test_av = std::string(TEST_MEDIA_PATH) + "/test_av.mp4";
        if (std::filesystem::exists(test_av)) {
            std::filesystem::copy_file(test_av, test_dir_ / "test_av.mp4",
                                       std::filesystem::copy_options::overwrite_existing);
        }
    }

    void TearDown() override {
        // Clean up test directory
        std::filesystem::remove_all(test_dir_);
    }

    std::filesystem::path test_dir_;
};

// Test for remuxing
TEST_F(TranscoderTest, Remux) {
    std::string inputFile = (test_dir_ / "test.mp4").string();
    std::string outputFile = (test_dir_ / "output.mp4").string();

    // For remuxing, don't set any codec parameters
    EncodeParameter encodeParams;
    ProcessParameter processParams;

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);
}

// Test for video transcoding
TEST_F(TranscoderTest, VideoTranscode) {
    std::string inputFile = (test_dir_ / "test.mp4").string();
    std::string outputFile = (test_dir_ / "output.mp4").string();

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set video encoding parameters
    encodeParams.set_video_codec_name("libx264");
    encodeParams.set_video_bit_rate(2000000); // 2Mbps

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);
}

// Test for audio transcoding
TEST_F(TranscoderTest, AudioTranscode) {
    std::string inputFile = (test_dir_ / "test.mp4").string();
    std::string outputFile = (test_dir_ / "output.aac").string();

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set audio encoding parameters
    encodeParams.set_video_codec_name(""); // Disable video
    encodeParams.set_audio_codec_name("aac");
    encodeParams.set_audio_bit_rate(128000); // 128kbps

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);
}

// Test for video cutting with copy mode (no re-encoding)
TEST_F(TranscoderTest, VideoCutCopyMode) {
    std::string inputFile = (test_dir_ / "test.mp4").string();
    std::string outputFile = (test_dir_ / "output_cut.mp4").string();

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set start and end time (cut first 1 second)
    encodeParams.set_start_time(0.0);
    encodeParams.set_end_time(1.0);
    // Leave codecs empty for copy mode

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);

    // Output file should be smaller than input file
    EXPECT_LT(std::filesystem::file_size(outputFile),
              std::filesystem::file_size(inputFile));
}

// Test for video cutting with transcoding
TEST_F(TranscoderTest, VideoCutWithTranscode) {
    std::string inputFile = (test_dir_ / "test.mp4").string();
    std::string outputFile = (test_dir_ / "output_cut_transcode.mp4").string();

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set start and end time (cut from 0.5s to 1.5s)
    encodeParams.set_start_time(0.5);
    encodeParams.set_end_time(1.5);

    // Set video codec for transcoding
    encodeParams.set_video_codec_name("libx264");
    encodeParams.set_audio_codec_name("aac");

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);
}

// Test for video cutting with invalid time range
TEST_F(TranscoderTest, VideoCutInvalidTimeRange) {
    std::string inputFile = (test_dir_ / "test.mp4").string();
    std::string outputFile = (test_dir_ / "output_invalid.mp4").string();

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set invalid time range (end time before start time)
    encodeParams.set_start_time(2.0);
    encodeParams.set_end_time(1.0);

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    // Should fail or produce empty/invalid output
    // Note: Actual behavior depends on FFmpeg implementation
    // This test documents the expected behavior
    if (result) {
        // If it succeeds, output should be very small or empty
        EXPECT_LE(std::filesystem::file_size(outputFile), 1000);
    }
}

// Test for video cutting with only start time
TEST_F(TranscoderTest, VideoCutStartTimeOnly) {
    std::string inputFile = (test_dir_ / "test.mp4").string();
    std::string outputFile = (test_dir_ / "output_start_only.mp4").string();

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set only start time (should cut from start time to end of video)
    encodeParams.set_start_time(0.5);
    // Don't set end time (defaults to -1.0)

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);

    // Output should be smaller than input
    EXPECT_LT(std::filesystem::file_size(outputFile),
              std::filesystem::file_size(inputFile));
}

// Test video cutting with duration (-t option)
TEST_F(TranscoderTest, VideoCutWithDuration) {
    std::string inputFile = (test_dir_ / "test.mp4").string();
    std::string outputFile = (test_dir_ / "output_cut_duration.mp4").string();

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set start time and calculate end time from duration
    // This simulates: -ss 0.5 -t 1.0 (cut from 0.5s for 1 second duration)
    double startTime = 0.5;
    double duration = 1.0;
    encodeParams.set_start_time(startTime);
    encodeParams.set_end_time(startTime + duration);  // endTime = 1.5s

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);

    // Output should be smaller than input
    EXPECT_LT(std::filesystem::file_size(outputFile),
              std::filesystem::file_size(inputFile));
}

// Test for PNG to JPG conversion
TEST_F(TranscoderTest, ImagePngToJpg) {
    std::string inputFile = (test_dir_ / "test.png").string();
    std::string outputFile = (test_dir_ / "output.jpg").string();

    // Skip test if input file doesn't exist
    if (!std::filesystem::exists(inputFile)) {
        GTEST_SKIP() << "Test PNG file not found, skipping test";
    }

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set JPEG encoding parameters
    encodeParams.set_video_codec_name("mjpeg");
    encodeParams.set_qscale(5);  // Quality setting
    encodeParams.set_pixel_format("yuvj444p");  // Full-range YUV for JPEG

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);
}

// Test for JPG to PNG conversion
TEST_F(TranscoderTest, ImageJpgToPng) {
    std::string inputFile = (test_dir_ / "test.jpg").string();
    std::string outputFile = (test_dir_ / "output.png").string();

    // Skip test if input file doesn't exist
    if (!std::filesystem::exists(inputFile)) {
        GTEST_SKIP() << "Test JPG file not found, skipping test";
    }

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set PNG encoding parameters
    encodeParams.set_video_codec_name("png");
    encodeParams.set_pixel_format("rgb24");  // RGB for PNG

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);
}

// Test for PNG to WebP conversion
TEST_F(TranscoderTest, ImagePngToWebp) {
    GTEST_SKIP() << "WebP encoder not available in this FFmpeg build";
}

// Test for JPG to WebP conversion
TEST_F(TranscoderTest, ImageJpgToWebp) {
    GTEST_SKIP() << "WebP encoder not available in this FFmpeg build";
}

// Test for image scaling (PNG resize)
TEST_F(TranscoderTest, ImageScaling) {
    std::string inputFile = (test_dir_ / "test.png").string();
    std::string outputFile = (test_dir_ / "output_scaled.png").string();

    // Skip test if input file doesn't exist
    if (!std::filesystem::exists(inputFile)) {
        GTEST_SKIP() << "Test PNG file not found, skipping test";
    }

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set PNG encoding with scaling
    encodeParams.set_video_codec_name("png");
    encodeParams.set_width(160);  // Scale to half size (assuming 320x240 input)
    encodeParams.set_height(120);
    encodeParams.set_pixel_format("rgb24");

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);

    // Verify the output dimensions are correct
    // Note: We don't check file size because PNG compression can vary
}

// Test for image pixel format conversion (RGB to grayscale)
TEST_F(TranscoderTest, ImagePixelFormatConversion) {
    std::string inputFile = (test_dir_ / "test.png").string();
    std::string outputFile = (test_dir_ / "output_gray.png").string();

    // Skip test if input file doesn't exist
    if (!std::filesystem::exists(inputFile)) {
        GTEST_SKIP() << "Test PNG file not found, skipping test";
    }

    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set PNG encoding with grayscale pixel format
    encodeParams.set_video_codec_name("png");
    encodeParams.set_pixel_format("gray");  // Convert to grayscale

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    EXPECT_TRUE(result);
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);

    // Grayscale image should be smaller than RGB
    EXPECT_LT(std::filesystem::file_size(outputFile),
              std::filesystem::file_size(inputFile));
}

// Test for transcoding file with stream 0 = audio, stream 1 = video
// This tests the fix for the filter graph initialization bug where filter_str
// was not being reset between streams, causing video filters to leak into audio filters
TEST_F(TranscoderTest, AudioVideoStreamOrder) {
    std::string inputFile = (test_dir_ / "test_av.mp4").string();
    std::string outputFile = (test_dir_ / "output_av.mp4").string();

    // Skip if test_av.mp4 doesn't exist
    if (!std::filesystem::exists(inputFile)) {
        GTEST_SKIP() << "test_av.mp4 not found, skipping test";
    }

    // Transcode the file with stream 0 = audio, stream 1 = video
    EncodeParameter encodeParams;
    ProcessParameter processParams;

    // Set video and audio encoding parameters
    encodeParams.set_video_codec_name("libx264");
    encodeParams.set_video_bit_rate(2000000); // 2Mbps
    encodeParams.set_audio_codec_name("aac");
    encodeParams.set_audio_bit_rate(128000); // 128kbps

    auto converter = std::make_unique<Converter>(&processParams, &encodeParams);
    converter->set_transcoder("FFMPEG");
    bool result = converter->convert_format(inputFile, outputFile);

    // Before the fix, this would fail with:
    // "Media type mismatch between the 'in' filter output pad 0 (video)
    //  and the 'Parsed_anull_0' filter input pad 0 (audio)"
    EXPECT_TRUE(result) << "Transcoding file with audio=stream0, video=stream1 should succeed";
    EXPECT_TRUE(std::filesystem::exists(outputFile));
    EXPECT_GT(std::filesystem::file_size(outputFile), 0);
}
